#include <algorithm>
#include <chrono>
#include <climits>
#include <cstddef>
#include <iterator>
#include <memory>
#include <ostream>
#include <pthread.h>
#include <iostream>
#include <stdexcept>
#include <utility>

#include "MyArray.h"

MyArray::MyArray(size_t size) : m_size(size), m_ptr(std::make_unique<int[]>(size))
{
    std::cout << "MyArray(size_t) constructor\n ";
}
MyArray::MyArray(std::initializer_list<int> list) : m_size(list.size()), m_ptr(std::make_unique<int[]>(list.size()))
{
    std::cout << "MyArray(std::initializer_list) constructor\n ";
    std::copy(std::begin(list), std::end(list), m_ptr.get());
}

MyArray::MyArray(const MyArray &arr) noexcept : m_size(arr.size()), m_ptr(std::make_unique<int[]>(arr.size()))
{
    std::cout << "MyArray(const MyArray &arr) constructor\n";
    const std::span<const int> source{ arr.m_ptr.get(), arr.size() };
    std::copy(std::begin(source), std::end(source), arr.m_ptr.get());
}

MyArray &MyArray::operator=(const MyArray &arr) noexcept
{
    MyArray tmp{ arr };
    swap(*this, tmp);
    return *this;
}

MyArray::MyArray(MyArray &&arr) noexcept : m_size{ std::exchange(arr.m_size, 0) }, m_ptr{ std::move(arr.m_ptr) }
{
    std::cout << "MyArray(const MyArray &&arr) constructor\n";
}

MyArray &MyArray::operator=(MyArray &&arr) noexcept
{
    if (this != &arr)
    {
        m_size = std::exchange(arr.m_size, 0);
        m_ptr = std::move(arr.m_ptr);
    }
    return *this;
}

size_t MyArray::size() const noexcept
{
    return m_size;
}

std::string MyArray::toString() const
{
    const std::span<const int> items{ m_ptr.get(), m_size };
    std::ostringstream output;
    output << "{";
    for (size_t count{ 0 }; const auto &item : items)
    {
        ++count;
        output << item << ((count < m_size) ? "," : "");
    }
    output << "}";
    return output.str();
}

bool MyArray::operator==(const MyArray &arr) const noexcept
{
    const std::span lhs = { this->m_ptr.get(), this->m_size };
    const std::span rhs = { arr.m_ptr.get(), arr.m_size };
    //return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
    return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs), std::end(rhs));
}

int &MyArray::operator[](size_t index)
{
    if (index > m_size)
    {
        throw std::out_of_range("index out of range");
    }
    return m_ptr[index];
}

const int &MyArray::operator[](size_t index) const
{
    if (index > m_size)
    {
        throw std::out_of_range("index out of range");
    }
    return m_ptr[index];
}

MyArray::operator bool() const noexcept
{
    return size() != 0;
}
MyArray &MyArray::operator++()
{
    std::span<int> items{ m_ptr.get(), m_size };
    for (auto &item : items)
    {
        ++item;
    }
    //std::for_each(std::begin(items), std::end(items), [](auto &item) { ++item; });
    return *this;
}
MyArray MyArray::operator++(int)
{
    MyArray temp(*this);
    ++(*this);
    return temp;
}

MyArray &MyArray::operator+=(int value)
{
    std::span<int> items{ m_ptr.get(), m_size };
    std::for_each(std::begin(items), std::end(items), [value](auto &item) { item += value; });
    return *this;
}

std::istream &operator>>(std::istream &in, MyArray &a)
{
    std::span<int> items{ a.m_ptr.get(), a.m_size };
    for (auto &item : items)
    {
        in >> item;
    }
    return in;
}

std::ostream &operator<<(std::ostream &out, MyArray &a)
{
    out << a.toString();
    return out;
}
void swap(MyArray &a, MyArray &b) noexcept
{
    std::swap(a.m_size, b.m_size);
    a.m_ptr.swap(b.m_ptr);  //unique_ptr function
}